/**
 * Actor Concurrency Benchmark
 * 
 * This benchmark tests the performance of the actor system under high concurrency.
 * It spawns many actors and measures how they perform when receiving messages concurrently.
 */
import { Kameo, ActorRef } from '../src/index';
import { loadConfig } from './config';
import { formatNumber, timeExecution, runConcurrently } from './utils';

/**
 * Interface for ping/pong messages
 */
interface PingPongMessage {
  type: 'ping' | 'pong';
  count?: number;
}

async function main() {
  console.log('🚀 Starting Actor Concurrency Benchmark');
  
  // Load benchmark configuration
  const config = loadConfig({
    // Override default config if needed
    connections: 50
  });
  
  console.log(`\nBenchmark Configuration:`);
  console.log(`- Concurrent Connections: ${config.connections}`);
  console.log(`- Actor Count: ${config.actorCount}`);
  
  // Initialize Kameo
  const kameo = new Kameo();
  await kameo.init();
  
  // Create a single actor for ping test
  console.log('\nInitializing ping actor...');
  const pingActor = kameo.spawn<PingPongMessage, number>('ping', { });
  
  // Create multiple actors for concurrency test
  console.log(`Creating ${config.actorCount} actors for concurrency test...`);
  const actors: ActorRef<PingPongMessage, number>[] = [];
  for (let i = 0; i < config.actorCount; i++) {
    actors.push(kameo.spawn<PingPongMessage, number>('ping', { }));
  }
  
  // Test 1: Single actor, sequential pings
  console.log('\n📊 Test 1: Single actor, sequential pings');
  const [sequentialResults, sequentialTime] = await timeExecution(async () => {
    const results = [];
    for (let i = 0; i < 1000; i++) {
      results.push(await pingActor.ask({ type: 'ping', count: i }));
    }
    return results;
  });
  
  console.log(`Sequential ping time: ${formatNumber(sequentialTime, 'ms')}`);
  console.log(`Requests per second: ${formatNumber((1000 / (sequentialTime / 1000)), 'req/sec')}`);
  
  // Test 2: Single actor, concurrent pings
  console.log('\n📊 Test 2: Single actor, concurrent pings');
  const [concurrentResults, concurrentTime] = await timeExecution(async () => {
    return Promise.all(Array.from({ length: 1000 }, (_, i) => 
      pingActor.ask({ type: 'ping', count: i })
    ));
  });
  
  console.log(`Concurrent ping time: ${formatNumber(concurrentTime, 'ms')}`);
  console.log(`Requests per second: ${formatNumber((1000 / (concurrentTime / 1000)), 'req/sec')}`);
  console.log(`Speedup factor: ${formatNumber(sequentialTime / concurrentTime, 'x')}`);
  
  // Test 3: Multiple actors, concurrent pings with varying connection counts
  console.log('\n📊 Test 3: Scalability test with varying concurrency');
  
  const concurrencyLevels = [1, 5, 10, 25, 50, 100].filter(n => n <= config.connections);
  
  for (const concurrency of concurrencyLevels) {
    const [_, scalabilityTime] = await timeExecution(async () => {
      // Run multiple rounds to get a stable measurement
      for (let round = 0; round < 5; round++) {
        // Use a subset of actors based on concurrency level
        const testActors = actors.slice(0, concurrency);
        
        // Send messages to all actors in parallel
        await Promise.all(testActors.map(actor => 
          actor.ask({ type: 'ping' })
        ));
      }
    });
    
    const timePerRequest = scalabilityTime / (5 * concurrency);
    const requestsPerSecond = 1000 / timePerRequest;
    
    console.log(`- Concurrency ${concurrency}:`);
    console.log(`  Time: ${formatNumber(scalabilityTime, 'ms')}`);
    console.log(`  Throughput: ${formatNumber(requestsPerSecond, 'req/sec')}`);
  }
  
  // Test 4: Load test with maximum concurrency
  console.log('\n📊 Test 4: Load test with maximum concurrency');
  
  // Warm up
  await runConcurrently(Math.min(100, config.actorCount), 
    async (i) => actors[i].tell({ type: 'ping' }));
  
  // Actual test
  const [loadResults, loadTime] = await timeExecution(async () => {
    return runConcurrently(config.connections, async (connIndex) => {
      const results = [];
      
      // Each connection sends 100 messages to random actors
      for (let i = 0; i < 100; i++) {
        const actorIndex = Math.floor(Math.random() * config.actorCount);
        results.push(await actors[actorIndex].ask({ type: 'ping' }));
      }
      
      return results;
    });
  });
  
  const totalRequests = config.connections * 100;
  const requestsPerSecond = totalRequests / (loadTime / 1000);
  
  console.log(`Load test results:`);
  console.log(`- Total requests: ${formatNumber(totalRequests)}`);
  console.log(`- Test duration: ${formatNumber(loadTime, 'ms')}`);
  console.log(`- Throughput: ${formatNumber(requestsPerSecond, 'req/sec')}`);
  console.log(`- Average latency: ${formatNumber(loadTime / totalRequests, 'ms/req')}`);
  
  console.log('\nBenchmark complete!');
}

// Run the benchmark
main().catch(error => {
  console.error('Benchmark failed:', error);
  process.exit(1);
}); 